/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IFilesConfigurationService, IAutoSaveConfiguration } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ILogService } from 'vs/platform/log/common/log';

export class BackupTracker extends Disposable implements IWorkbenchContribution {

	// Disable backup for when a short auto-save delay is configured with
	// the rationale that the auto save will trigger a save periodically
	// anway and thus creating frequent backups is not useful
	//
	// This will only apply to working copies that are not untitled where
	// auto save is actually saving.
	private static DISABLE_BACKUP_AUTO_SAVE_THRESHOLD = 1500;

	// Delay creation of backups when content changes to avoid too much
	// load on the backup service when the user is typing into the editor
	protected static BACKUP_FROM_CONTENT_CHANGE_DELAY = 1000;

	private backupsDisabledForAutoSaveables = false;

	private readonly pendingBackups = new Map<IWorkingCopy, IDisposable>();

	constructor(
		@IBackupFileService private readonly backupFileService: IBackupFileService,
		@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
		@IWorkingCopyService private readonly workingCopyService: IWorkingCopyService,
		@ILogService private readonly logService: ILogService
	) {
		super();

		// Figure out initial auto save config
		this.onAutoSaveConfigurationChange(filesConfigurationService.getAutoSaveConfiguration());

		this.registerListeners();
	}

	private registerListeners() {

		// Working Copy events
		this._register(this.workingCopyService.onDidRegister(c => this.onDidRegister(c)));
		this._register(this.workingCopyService.onDidUnregister(c => this.onDidUnregister(c)));
		this._register(this.workingCopyService.onDidChangeDirty(c => this.onDidChangeDirty(c)));
		this._register(this.workingCopyService.onDidChangeContent(c => this.onDidChangeContent(c)));

		// Listen to auto save config changes
		this._register(this.filesConfigurationService.onAutoSaveConfigurationChange(c => this.onAutoSaveConfigurationChange(c)));
	}

	private onDidRegister(workingCopy: IWorkingCopy): void {
		this.scheduleBackup(workingCopy);
	}

	private onDidUnregister(workingCopy: IWorkingCopy): void {
		this.discardBackup(workingCopy);
	}

	private onDidChangeDirty(workingCopy: IWorkingCopy): void {
		if (!workingCopy.isDirty()) {
			this.discardBackup(workingCopy);
		}
	}

	private onDidChangeContent(workingCopy: IWorkingCopy): void {
		if (workingCopy.isDirty()) {
			this.scheduleBackup(workingCopy);
		}
	}

	private onAutoSaveConfigurationChange(configuration: IAutoSaveConfiguration): void {
		this.backupsDisabledForAutoSaveables = typeof configuration.autoSaveDelay === 'number' && configuration.autoSaveDelay < BackupTracker.DISABLE_BACKUP_AUTO_SAVE_THRESHOLD;
	}

	private scheduleBackup(workingCopy: IWorkingCopy): void {
		if (this.backupsDisabledForAutoSaveables && !(workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) {
			return; // skip if auto save is enabled with a short delay
		}

		// Clear any running backup operation
		dispose(this.pendingBackups.get(workingCopy));
		this.pendingBackups.delete(workingCopy);

		this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString());

		// Schedule new backup
		const handle = setTimeout(() => {

			// Clear disposable
			this.pendingBackups.delete(workingCopy);

			// Backup if dirty
			if (workingCopy.isDirty()) {
				this.logService.trace(`[backup tracker] running backup`, workingCopy.resource.toString());

				workingCopy.backup();
			}
		}, BackupTracker.BACKUP_FROM_CONTENT_CHANGE_DELAY);

		// Keep in map for disposal as needed
		this.pendingBackups.set(workingCopy, toDisposable(() => {
			this.logService.trace(`[backup tracker] clearing pending backup`, workingCopy.resource.toString());

			clearTimeout(handle);
		}));
	}

	private discardBackup(workingCopy: IWorkingCopy): void {
		this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString());

		// Clear any running backup operation
		dispose(this.pendingBackups.get(workingCopy));
		this.pendingBackups.delete(workingCopy);

		// Forward to backup file service
		this.backupFileService.discardResourceBackup(workingCopy.resource);
	}
}
